using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace Campus
{    
    public class Level
    {
        #region Fields and Properties

        /// <summary>
        /// The selection circle to draw around selected units and buildings
        /// </summary>
        public static Texture2D UnitSelectionCircle;
        public static Texture2D BuildingSelectionCircle;

        public static Dictionary<Type, Rectangle> SelectionRectangles = new Dictionary<Type, Rectangle>()
        {
            // Units
            { typeof(Biologist), new Rectangle(7, 40, 35, 19) },
            { typeof(MechanicalEngineer), new Rectangle(5, 38, 39, 21) },
            { typeof(Physicist), new Rectangle(10, 48, 39, 21) },
            { typeof(Robot), new Rectangle(8, 52, 54, 26) },
            { typeof(CampusShuttle), new Rectangle(-8, 65, 110, 25) },
            { typeof(ComputerScientist), new Rectangle(10, 48, 39, 21) },
            // Buildings
            { typeof(Dorm), new Rectangle() },
            { typeof(Library), new Rectangle() },
            { typeof(CollegeOfEngineering), new Rectangle(-5, -58, 200, 120) },
            { typeof(Scholarship), new Rectangle(-5, -58, 200, 120) },
            { typeof(InterdimensionalPortal), new Rectangle(-5, -58, 200, 120) },
            { typeof(CleanRoom), new Rectangle(5, -78, 220, 130) },
            { typeof(Dispatch), new Rectangle(-5, -58, 200, 120) },
            { typeof(GatewayToHell), new Rectangle(-5, -58, 264, 159) },
            { typeof(ExperimentalLaboratory), new Rectangle(-5, -58, 200, 120) },
            { typeof(DownByTheRiver), new Rectangle(-5, -83, 264, 159) },
            { typeof(MothersBasement), new Rectangle(-5, -53, 250, 145) },
            { typeof(SchoolCafeteria), new Rectangle(-5, -58, 200, 120) }

        };

        /// <summary>
        /// updateWorld is used to determine whether or not we need to redraw the world.
        /// Drawing the world every time Draw() is called is very resource intensive.
        /// </summary>
        private bool m_updateWorld = false;
        public bool UpdateWorld
        {
            get { return m_updateWorld; }
            set { m_updateWorld = value; }
        }

        /// <summary>
        /// the list of both the opponents and your units and structures
        /// </summary>
        private List<BaseEntity> m_baseEntities = new List<BaseEntity>();
        public List<BaseEntity> BaseEntities
        {
            get { return m_baseEntities; }
            set { m_baseEntities = value; }
        }

        /// <summary>
        /// The terrainmap for the world grid.
        /// </summary>
        private Terrain[,] m_terrainMap;
        public Terrain[,] TerrainMap
        {
            get { return m_terrainMap; }
            set { m_terrainMap = value; }
        }

        /// <summary>
        /// The collection of Sprites in a level, represented as a List of Sprite Objects
        /// </summary>
        private List<Sprite> m_spriteCollection = new List<Sprite>();
        public List<Sprite> SpriteCollection
        {
            get { return m_spriteCollection; }
            set { m_spriteCollection = value; }
        }

        /// <summary>
        /// The collection of sprites in the level that make up terrain that has dynamic layer depth.
        /// </summary>
        public List<Sprite> ForegroundTerrainCollection
        {
            get { return m_foregroundCollection; }
        }
        private List<Sprite> m_foregroundCollection = new List<Sprite>();

        /// <summary>
        /// The collection of sprites in the level that make up terrain that is always drawn behind everything else.
        /// </summary>
        public List<Sprite> BackgroundTerrainCollection
        {
            get { return m_backgroundCollection; }
        }
        private List<Sprite> m_backgroundCollection = new List<Sprite>();

        /// <summary>
        /// The mapping of sprites to particular minimap colors.
        /// </summary>
        public Dictionary<Sprite, Color> MinimapColors
        {
            get { return m_minimapColors; }
            set { m_minimapColors = value; }
        }
        private Dictionary<Sprite, Color> m_minimapColors = new Dictionary<Sprite, Color>();

        private Dictionary<Sprite, Vector2> m_terrainGridPositions = new Dictionary<Sprite, Vector2>();

        /// <summary>
        /// This allows the screen height to be set
        /// </summary>
        private Rectangle m_viewableArea = new Rectangle(0, 0, 
                                                         ResolutionHandler.WindowWidth, 
                                                         ResolutionHandler.WindowHeight);
        public Rectangle ViewableArea {
            get { return m_viewableArea; }
            set { m_viewableArea = value; }
        } 

        /// <summary>
        /// This may be used to set the background color (behind the background image)
        /// </summary>
        private Color m_backgroundColor = new Color();
        public Color BackgroundColor {
            get { return m_backgroundColor; }
            set { m_backgroundColor = value; }
        }

        private int m_tileSize = 20;
        public int TileSize
        {
            get { return m_tileSize; }
            set { m_tileSize = value; }
        }

        /// <summary>
        /// This Level's World object.
        /// </summary>
        private World m_world;
        public World WorldObject
        {
            get { return m_world; }
        }

        /// <summary>
        /// This Level's list of Tiles that it uses to draw the corresponding terrain types found in the World object.
        /// </summary>
        private Dictionary<Terrain, Tile> m_tiles;
        public Dictionary<Terrain, Tile> Tiles
        {
            get { return m_tiles; }
        }

        /// <summary>
        /// The parent of this Level object; or the object that keeps a reference to it.
        /// </summary>
        private Campus m_parent;
        public Campus Parent
        {
            get { return m_parent; }
        }

        /// <summary>
        /// The distance (in pixels) between the origins in each terrain tile to be drawn.  That is, how much farther in the X and Y to draw the next tile in the row.
        /// </summary>
        private Vector2 m_tileOffset;
        public Vector2 TileOffset
        {
            get { return m_tileOffset; }
        }

        /// <summary>
        /// The value to multiply the draw size with when the tile is drawn (for uniform scaling).
        /// </summary>
        private float m_drawScale;
        public float DrawScale
        {
            get { return m_drawScale; }
        }

        /// <summary>
        /// The size, in pixels of the level.
        /// </summary>
        public Vector2 PixelsInLevel { get; private set; }

        /// <summary>
        /// The list of Players currently on this map.
        /// </summary>
        public List<Player> Players
        {
            get { return m_players; }
        }

        private List<Player> m_players = new List<Player>();
        private Dictionary<Point, Vector2> m_tileCenters;

        #endregion

        #region Constructors

        /// <summary>
        /// Builds an isometric level from the provided tile set (Terrain enum objects mapped to Tile objects) and mapString with the provided base tile size.
        /// </summary>
        /// <param name="parent">The parent of this Level object; or the object that keeps a reference to it.</param>
        /// <param name="tiles">This Level's list of Tiles that it uses to draw the corresponding terrain types found in the World object.</param>
        /// <param name="mapString">The string (usually from XML) to be loaded into this Level.</param>
        /// <param name="distanceBetweenTiles">The distance (in pixels) to put between the origin of each tile that gets drawn.</param>
        /// <param name="drawScale">The value to multiply the draw size with when the tile is drawn (for uniform scaling).</param>
        public Level(Campus parent, Dictionary<Terrain, Tile> tiles, string mapString, Vector2 distanceBetweenTiles, float drawScale)
        {
            m_parent = parent;
            m_tiles = tiles;

            List<Terrain> unwalkableTiles = new List<Terrain>();

            foreach (KeyValuePair<Terrain, Tile> kvp in tiles)
            {
                if (!kvp.Value.IsWalkable)
                {
                    unwalkableTiles.Add(kvp.Key);
                }
            }
            m_world = new World(this, mapString, unwalkableTiles);
            
            m_tileOffset = distanceBetweenTiles;
            m_drawScale = drawScale;

            PixelsInLevel = GetLevelSizeInPixels();

            // Populate the Sprite Batch used to draw the terrain now that we know where everything is placed.
            BuildTerrainSpriteCollection();

            Cursor.SCROLL_LIMIT_XMAX = (int)(PixelsInLevel.X - ResolutionHandler.WindowWidth + 100);
            Cursor.SCROLL_LIMIT_YMAX = (int)(PixelsInLevel.Y - ResolutionHandler.WindowHeight + 200);

            m_tileCenters = new Dictionary<Point, Vector2>();

            foreach (Vector2 tileGridPosition in m_terrainGridPositions.Values)
            {
                Point gridPoint = new Point((int)tileGridPosition.X, (int)tileGridPosition.Y);

                if (m_tileCenters.ContainsKey(gridPoint))
                {
                    continue;
                }

                Vector2 tempVector2;

                CalculatePointForGridPosition(gridPoint, out tempVector2);

                m_tileCenters.Add(gridPoint, tempVector2);
            }
        }

        #endregion

        #region Other Functions

        /// <summary>
        /// Updates the background image if it is meant to scroll and also updates the Sprite positions
        /// (by calling the Update method of each sprite).
        /// </summary>
        public void Update(GameTime gameTime)
        {
            foreach (Sprite s in m_baseEntities)
                s.Update(gameTime, true, true);

            foreach (Sprite s in m_spriteCollection) {
                s.Update(gameTime, true, true);
            }

            DoAllCollisions();
        }

        /// <summary>
        /// Draws the visible portion of the background and all the sprites within the visible portion
        /// </summary>
        public void Draw(SpriteBatch spriteBatch, GraphicsDeviceManager graphics, Player player)
        {
            Vector2 screenPosition = player.ScreenPosition;
            int viewableAreaWidth = ViewableArea.Width;
            int viewableAreaHeight = ViewableArea.Height;

            // Clear the Player's list of visible sprites.  We'll repopulate it as we draw.
            player.VisibleSprites.Clear();

            // Draw static background world.
            spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.BackToFront, SaveStateMode.None);
            foreach (Sprite s in m_backgroundCollection)
            {
                // Store the position for faster comparison.
                float positionX = s.Position.X;
                float positionY = s.Position.Y;

                // Determine draw size from the size and the scale for this Level.
                float drawWidth = s.Size.X * m_drawScale;
                float drawHeight = s.Size.Y * m_drawScale;

                // Determine whether or not the tile is within view.
                Color color = new Color(100, 100, 100);
                if (SpriteIsVisible(s, player))
                {
                    color = s.Color;
                    player.VisibleSprites.Add(s);
                }

                if (s.IsVisible &&
                    positionX >= (screenPosition.X - drawWidth) && positionX < (screenPosition.X + viewableAreaWidth + (s.Origin.X * m_drawScale)) &&
                    positionY >= (screenPosition.Y - drawHeight) && positionY < (screenPosition.Y + viewableAreaHeight + (s.Origin.Y * m_drawScale)))
                {
                    Rectangle destinationRect = new Rectangle((int)(positionX - screenPosition.X), (int)(positionY - screenPosition.Y), (int)drawWidth, (int)drawHeight);
                    Rectangle sourceRect = new Rectangle(0, 0, s.Texture.Width, s.Texture.Height);
                    float rotation = 0;

                    spriteBatch.Draw(s.Texture, destinationRect, sourceRect, color, rotation, s.Origin, s.Effects, s.LayerDepth);
                }
            }
            spriteBatch.End();

            // Draw dynamic world.
            spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.BackToFront, SaveStateMode.None);
            foreach (Sprite s in m_foregroundCollection)
            {
                // Store the position for faster comparison.
                float positionX = s.Position.X;
                float positionY = s.Position.Y;

                // Determine draw size from the size and the scale for this Level.
                float drawWidth = s.Size.X * m_drawScale;
                float drawHeight = s.Size.Y * m_drawScale;

                // Determine whether or not the tile is within view.
                Color color = new Color(100, 100, 100);
                if (SpriteIsVisible(s, player))
                {
                    color = s.Color;
                    player.VisibleSprites.Add(s);
                }

                if (s.IsVisible &&
                    positionX >= (screenPosition.X - drawWidth) && positionX < (screenPosition.X + viewableAreaWidth + (s.Origin.X * m_drawScale)) &&
                    positionY >= (screenPosition.Y - drawHeight) && positionY < (screenPosition.Y + viewableAreaHeight + (s.Origin.Y * m_drawScale)))
                {
                    Rectangle destinationRect = new Rectangle((int)(positionX - screenPosition.X), (int)(positionY - screenPosition.Y), (int)drawWidth, (int)drawHeight);
                    Rectangle sourceRect = new Rectangle(0, 0, s.Texture.Width, s.Texture.Height);
                    float rotation = 0;

                    spriteBatch.Draw(s.Texture, destinationRect, sourceRect, color, rotation, s.Origin, s.Effects, s.LayerDepth);
                }
            }

            // Draw BaseEntity (interactable) objects.
            foreach (BaseEntity be in m_baseEntities)
            {
                // Get the type of base entity for future reference
                Type beType = be.GetType();

                // Store the position for faster comparison.
                float positionX = be.Position.X;
                float positionY = be.Position.Y;

                // TODO: Figure out where the draw size SHOULD come from.  This is winged for now.
                // Determine draw size from the size and the scale for this Level.
                float drawWidth = be.Size.X * m_drawScale;
                float drawHeight = be.Size.Y * m_drawScale;

                // Determine whether or not the BaseEntity is within view of the Player.
                bool entityIsWithinVisibleRange = false;
                if (SpriteIsVisible(be, player))
                {
                    entityIsWithinVisibleRange = true;
                    player.VisibleSprites.Add(be);
                }

                // Determine the y-position of the bottom of the texture to get the proper z-value.
                //Vector2 bottomOfTexture = be.Position;
                //bottomOfTexture.Y = bottomOfTexture.Y - be.Origin.Y + be.Size.Y;

                // Determine laydepth of the entity to be drawn.
                float layerDepth = Utilities.GetZByScreenPosition(be.Position, this.PixelsInLevel.Y);

                if (be is Unit)
                    layerDepth = Utilities.GetZByScreenPosition(new Vector2(be.Position.X + be.Size.X, be.Position.Y + be.Size.Y), this.PixelsInLevel.Y);
                
                // Determine if the entity is visible before drawing it.
                if (be.Texture != null && be.IsVisible && entityIsWithinVisibleRange &&
                    positionX >= (screenPosition.X - drawWidth) && positionX < (screenPosition.X + viewableAreaWidth + (be.Origin.X * m_drawScale)) &&
                    positionY >= (screenPosition.Y - drawHeight) && positionY < (screenPosition.Y + viewableAreaHeight + (be.Origin.Y * m_drawScale)))
                {
                    Rectangle destinationRect = new Rectangle((int)(be.Position.X - screenPosition.X), (int)(be.Position.Y - screenPosition.Y), (int)drawWidth, (int)drawHeight);

                    // Draw the texture
                    spriteBatch.Draw(be.Texture, destinationRect, null, Color.White, be.Rotation, be.Origin, SpriteEffects.None, layerDepth);
                    // If there's a team color image as well, then draw that too.
                    if (be.TeamColorTexture != null && be.IsUsable)
                        spriteBatch.Draw(be.TeamColorTexture, destinationRect, null, be.PlayerOwner.TeamColor, be.Rotation, be.Origin, SpriteEffects.None, layerDepth - 0.00001f);

                    if (be.IsSelected)
                    {
                        Color selectColor = Color.White;

                        if (be.TeamNumber == player.TeamNumber)
                        {
                            selectColor = Color.GreenYellow;
                        }
                        else 
                        {
                            selectColor = Color.Red;
                        }

                        // Get the rectangle for the appropriate base entity
                        Rectangle destRect;
                        if (SelectionRectangles.ContainsKey(beType))
                        {
                            destRect = SelectionRectangles[be.GetType()];
                        }
                        else
                        {
                            destRect = new Rectangle();
                        }

                        destRect.X += (int)(be.Position.X - player.ScreenPosition.X);
                        destRect.Y += (int)(be.Position.Y - player.ScreenPosition.Y);

                        if (be is Unit && beType != typeof(CampusShuttle))
                        {
                            spriteBatch.Draw(Level.UnitSelectionCircle, destRect, null, selectColor, 0.0f, Vector2.Zero, SpriteEffects.None, layerDepth + 0.0000001f);
                        }
                        else
                        {
                            spriteBatch.Draw(Level.BuildingSelectionCircle, destRect, null, selectColor, 0.0f, Vector2.Zero, SpriteEffects.None, layerDepth + 0.0000001f);
                        }
                    }
                    
                    // DEBUG
                    // Draw inside bounding sphere (to tell where your bounding sphere sits for a particular object)
                    //Rectangle boundingSphereRect = new Rectangle((int)(be.BoundingSphere.Center.X - be.BoundingSphere.Radius + be.Origin.X - screenPosition.X), (int)(be.BoundingSphere.Center.Y - be.BoundingSphere.Radius + be.Origin.Y - screenPosition.Y), (int)be.BoundingSphere.Radius * 2, (int)be.BoundingSphere.Radius * 2);
                    //spriteBatch.Draw(be.Texture, boundingSphereRect, sourceRect, be.Color, be.Rotation, be.Origin, SpriteEffects.None, layerDepth - 0.1f);
                    // DEBUG
                }
            }

            // Draw Sprite (non-interactable) objects.
            foreach (Sprite s in m_spriteCollection)
            {
                // Store the position for faster comparison.
                float positionX = s.Position.X;
                float positionY = s.Position.Y;

                // TODO: Figure out where the draw size SHOULD come from.  This is winged for now.
                // Determine draw size from the size and the scale for this Level.
                float drawWidth = s.Size.X * m_drawScale;
                float drawHeight = s.Size.Y * m_drawScale;

                // Determine whether or not the Sprite is within view of the Player.
                bool entityIsWithinVisibleRange = false;
                if (SpriteIsVisible(s, player))
                {
                    entityIsWithinVisibleRange = true;
                    player.VisibleSprites.Add(s);
                }

                // Get the proper z-value.
                float layerDepth = Utilities.GetZByScreenPosition(s.Position, PixelsInLevel.Y);

                if (s.Texture != null && s.IsVisible && entityIsWithinVisibleRange &&
                    positionX >= (screenPosition.X - drawWidth) && positionX < (screenPosition.X + viewableAreaWidth) &&
                    positionY >= (screenPosition.Y - drawHeight) && positionY < (screenPosition.Y + viewableAreaHeight))
                {
                    Rectangle destinationRect = new Rectangle((int)(s.Position.X - screenPosition.X), (int)(s.Position.Y - screenPosition.Y), (int)drawWidth, (int)drawHeight);
                    Rectangle sourceRect = new Rectangle(0, 0, s.Texture.Width, s.Texture.Height);

                    spriteBatch.Draw(s.Texture, destinationRect, sourceRect, s.Color, s.Rotation, s.Origin, SpriteEffects.None, layerDepth);
                }
            }
            spriteBatch.End();
        }

        public bool SpriteIsVisible(Sprite sprite, Player player)
        {
            BoundingBox spriteBox = new BoundingBox(new Vector3(new Vector2(sprite.Position.X - sprite.Origin.X, sprite.Position.Y - sprite.Origin.Y), 0.0f), new Vector3(sprite.Position.X - sprite.Origin.X + sprite.Size.X, sprite.Position.Y - sprite.Origin.Y + sprite.Size.Y, 0.0f));

            foreach (Unit unit in player.Units)
            {
                BoundingSphere unitSphere = new BoundingSphere(new Vector3(unit.Position, 0.0f), unit.VisionRadius);

                if (unitSphere.Contains(spriteBox) != ContainmentType.Disjoint)
                    return true;

                //Vector2 distance = new Vector2(Math.Abs((unit.Position.X - unit.Origin.X + unit.Size.X / 2) - (sprite.Position.X - sprite.Origin.X + sprite.Size.X / 2)), Math.Abs((unit.Position.Y - unit.Origin.Y + unit.Size.Y / 2) - (sprite.Position.Y - sprite.Origin.Y + sprite.Size.Y / 2)));

                //if (distance.Length() < unit.VisionRadius)
                //    return true;
            }

            foreach (Structure structure in player.Structures)
            {
                BoundingSphere structureSphere = new BoundingSphere(new Vector3(structure.Position, 0.0f), structure.VisionRadius);

                if (structureSphere.Contains(spriteBox) != ContainmentType.Disjoint)
                    return true;

               //Vector2 distance = new Vector2(Math.Abs((structure.Position.X - structure.Origin.X + structure.Size.X / 2) - (sprite.Position.X - sprite.Origin.X + sprite.Size.X / 2)), Math.Abs((structure.Position.Y - structure.Origin.Y + structure.Size.Y / 2) - (sprite.Position.Y - sprite.Origin.Y + sprite.Size.Y / 2)));

               //if (distance.Length() < structure.VisionRadius)
               //    return true;
            }

            return false;
        }

        /// <summary>
        /// Iterate through all the sprites, check if any are colliding, and run their doCollision()
        /// </summary>
        public void DoAllCollisions()
        {
            // Do collisions between units on the map.

            for (int i = 0; i < m_baseEntities.Count; i++)
            {
                for (int j = i + 1; j < m_baseEntities.Count; j++)
                {
                    BaseEntity be1 = m_baseEntities[i];
                    BaseEntity be2 = m_baseEntities[j];

                    var u1 = be1 as Unit;
                    var u2 = be2 as Unit;

                    if (u1 != null && u2 != null && u1.CollidesWithOtherUnit(u2))
                    {
                        u1.DoCollision(u1, u2);
                    }
                }
            }
        }

        /// <summary>
        /// Sets the size and location of the viewable area (in pixels)
        /// </summary>
        public void SetViewableArea(int x, int y, int width, int height)
        {
            Rectangle newViewArea = new Rectangle(x, y, width, height);

            ViewableArea = newViewArea;
        }

        private void CalculatePointForGridPosition(Point gridPosition, out Vector2 result)
        {
            foreach (KeyValuePair<Sprite, Vector2> kvp in m_terrainGridPositions)
            {
                Point currentGridPosition = new Point((int)kvp.Value.X, (int)kvp.Value.Y);

                if (currentGridPosition == gridPosition)
                {
                    result = new Vector2(kvp.Key.Position.X + kvp.Key.Size.X / 2, kvp.Key.Position.Y);
                    return;
                }
            }
            result = new Vector2(-1, -1);
        }

        public bool GetPointForGridPosition(Point gridPosition, out Vector2 result)
        {
            if (m_tileCenters.ContainsKey(gridPosition))
            {
                result = m_tileCenters[gridPosition];
                return true;
            }
            else
            {
                result = new Vector2(-1, -1);
                return false;
            }
        }

        /// <summary>
        /// Returns the grid position that the provided Vector2 position (in pixels) falls in.
        /// </summary>
        /// <param name="position">Vector2 containing the position (in pixels) to get the grid position for (mouse position + player's screen position).</param>
        /// <returns>Vector2 which represents the x and y grid position of the tile at that pixel's position.  (-1, -1) if the point was not on a tile.</returns>
        public bool GetGridPositionForPoint(Vector2 position, out Vector2 result)
        {
            float leastDistance = int.MaxValue;
            Sprite closest = null;

            foreach (KeyValuePair<Sprite, Vector2> kvp in m_terrainGridPositions)
            {
                Sprite s = kvp.Key;
                Rectangle rect = new Rectangle((int)s.Position.X, (int)(s.Position.Y - s.Size.Y / 2), (int)s.Size.X, (int)s.Size.Y);

                //s.Color = Color.White;

                if (rect.Contains((int)position.X, (int)position.Y))
                {
                    float distanceLength = new Vector2(Math.Abs(rect.Center.X - position.X), Math.Abs(rect.Center.Y - position.Y)).Length();

                    if (distanceLength < leastDistance)
                    {
                        leastDistance = distanceLength;
                        closest = s;
                    }
                }
            }

            if (closest != null)
            {
                //closest.Color = Color.Black;
                result = m_terrainGridPositions[closest];
                return true;
            }
            else
            {
                result = new Vector2(-1, -1);
                return false;
            }
        }

        /// <summary>
        /// Returns the origin (the position from which a tile is drawn) for the tile at the provided row and column.
        /// </summary>
        /// <param name="row">Row of the tile to get the origin for.</param>
        /// <param name="column">Column of the tile to get the origin for.</param>
        /// <returns>Vector2 which represents the x and y position (in pixels) of the tile at (row, column).</returns>
        public Vector2 GetOriginOfTile(int row, int column)
        {
            Vector2 pixelPosition = new Vector2(-1, -1);

            // The starting Y is given by the distance between tile origins multiplied by the number of columns in the world.
            float startYAdjustment = PixelsInLevel.Y / 2;

            // Adjust for row.
            pixelPosition.X = m_tileOffset.X * m_drawScale * row;
            pixelPosition.Y = m_tileOffset.Y * m_drawScale * row + startYAdjustment;

            // Adjust for column.
            pixelPosition.X += m_tileOffset.X * m_drawScale * column;
            pixelPosition.Y -= m_tileOffset.Y * m_drawScale * column;

            return pixelPosition;
        }

        /// <summary>
        /// Returns a boolean indicating if the tiles in the list relative to the grid position are not walls.
        /// </summary>
        /// <param name="gridPosition">Vector2 representing the grid position in (row, col) fashion.</param>
        /// <param name="tiles">List of Vector2s representing the tiles to check relative to the provided grid position.</param>
        /// <returns>A boolean indicating if the tiles are available.</returns>
        public bool TilesAreAvailable(Vector2 gridPosition, List<Vector2> tiles)
        {
            int row = (int)gridPosition.X;
            int column = (int)gridPosition.Y;

            foreach (Vector2 v in tiles)
            {
                if (IsWall(row + (int)v.Y, column + (int)v.X))
                    return false;
            }

            return true;
        }

        #region On-Level Entity Manipulation Functions

        /// <summary>
        /// Adds a BaseEntity to a specific tile.  If the provided row and column reference an occupied tile space, the BaseEntity will be added to the nearest open one.
        /// </summary>
        /// <param name="newEntity">Base Entity to be added.</param>
        /// <param name="row">Row of the level to be added on.</param>
        /// <param name="column">Column of the level to be added on.</param>
        /// <returns>True if a position to place the BaseEntity was found.</returns>
        public bool AddBaseEntityToTile(BaseEntity newEntity, int row, int column)
        {
            // Position the BaseEntity according to the provided tile.
            Vector2 newPosition;
            Vector2 start = new Vector2(column, row);
            Vector2 end = new Vector2(column, row);

            // Get the nearest open tile to get the new entity's position.
            if (GetNearestOpenTile(start, end, out newPosition))
                newEntity.Position = newPosition;
            else
                return false;

            // If it's a building, make sure it gets added to the wall map.
            if (newEntity is Structure)
            {
                Structure newStructure = (Structure)newEntity;
                newStructure.GridPosition = new Vector2(column, row);
                AddToWallMap(row, column, newStructure.TilesOccupied);
            }

            // Set the collision action appropriately.
            newEntity.CollisionAction = EntityCollisionAction;

            newEntity.PlayerOwner.Added.Add(newEntity);
            //m_baseEntities.Add(newEntity);

            return true;
        }

        /// <summary>
        /// Add a BaseEntity to the Level with the position it has.
        /// 
        /// Note: Structures should NOT be added via this function as they will be added without regard to the tiles they occupy (no walls will be generated).
        /// </summary>
        /// <param name="baseEntity">BaseEntity object to be added.</param>
        public void AddBaseEntity(BaseEntity baseEntity)
        {
            // Set the collision action appropriately.
            baseEntity.CollisionAction = EntityCollisionAction;

            m_baseEntities.Add(baseEntity);
        }

        /// <summary>
        /// Remove a BaseEntity from the level.
        /// </summary>
        public void RemoveBaseEntity(BaseEntity baseEntity)
        {
            // If the BaseEntity is a Structure, we need to remove it from the wall map as well.
            if (baseEntity is Structure)
            {
                Structure removeStructure = (Structure)baseEntity;
                Vector2 gridPosition = removeStructure.GridPosition;
                RemoveFromWallMap((int)gridPosition.Y, (int)gridPosition.X, removeStructure.TilesOccupied);
            }

            m_baseEntities.Remove(baseEntity);
        }

        /// <summary>
        /// Adds a Sprite to a specific tile.  If the provided row and column reference an occupied tile space, the Sprite will be added to the nearest open one.
        /// </summary>
        /// <param name="newSprite">Sprite to be added.</param>
        /// <param name="row">Row of the level to be added on.</param>
        /// <param name="column">Column of the level to be added on.</param>
        /// <returns>True if a position to place the Sprite was found.</returns>
        public bool AddSpriteToTile(Sprite newSprite, int row, int column)
        {
            // Position the Sprite according to the provided tile.
            Vector2 newPosition;
            Vector2 start = new Vector2(column, row);
            Vector2 end = new Vector2(column, row);

            // Get the nearest open tile to get the new Sprite's position.
            if (GetNearestOpenTile(start, end, out newPosition))
                newSprite.Position = newPosition;
            else
                return false;

            newSprite.CollisionAction = SpriteCollisionAction;

            m_spriteCollection.Add(newSprite);

            return true;
        }

        /// <summary>
        /// Add a Sprite to the Level with the position it has.  The added object will not have HP or other interactability.  Use AddBaseEntityToTile() to add an interactable object.
        /// </summary>
        /// <param name="newSprite">The Sprite to be added.</param>
        public void AddSprite(Sprite newSprite)
        {
            newSprite.CollisionAction = SpriteCollisionAction;

            m_spriteCollection.Add(newSprite);
        }

        /// <summary>
        /// Remove a Sprite from the Level.
        /// </summary>
        /// <param name="s">The Sprite to be removed.</param>
        public void RemoveSprite(Sprite s)
        {
            m_spriteCollection.Remove(s);
        }

        #endregion

        #region Private Support Functions

        /// <summary>
        /// This function builds the SpriteCollection which contains the terrain Sprite/Tile stuff.
        /// 
        /// It does so by collecting the terrain listings from the World's terrain grid and building a Sprite for each tile.
        /// 
        /// Layered tiles are added such that the "lowest" tile on the stack has the base z-value and each tile on top of that has an additional +.01 layer depth than the last one.
        /// </summary>
        private void BuildTerrainSpriteCollection()
        {
            int rows = m_world.Height;
            int cols = m_world.Width;

            for (int r = 0; r < rows; r++)
            {
                for (int c = 0; c < cols; c++)
                {
                    List<Terrain> currentTerrainList = m_world.TerrainGrid[r, c];

                    for (int i = 0; i < currentTerrainList.Count; i++)
                    {
                        Terrain currentTerrain = currentTerrainList[i];
                        Tile currentTile = null;

                        List<Sprite> targetCollection = m_foregroundCollection;

                        // Get the tile associated with that terrain type; default to undefined if it's not found.
                        if (m_tiles.ContainsKey(currentTerrain))
                            currentTile = m_tiles[currentTerrain];
                        else
                            currentTile = m_tiles[Terrain.Undefined];

                        // Calculate the values to be passed to the Sprite constructor
                        Texture2D texture = currentTile.Texture;
                        Vector2 position = GetOriginOfTile(r, c);
                        Vector2 drawSize = new Vector2(currentTile.DrawSize.X, currentTile.DrawSize.Y);
                        Vector2 origin = new Vector2(currentTile.Origin.X, currentTile.Origin.Y);
                        Color color = currentTile.TintColor;
                        SpriteEffects effects = currentTile.Effects;
                        float layerDepth = currentTile.LayerDepth;
                        // Make sure that this tile's z-value is less than the tiles on top of it (according to tile layering as defined in the map file).
                        float layerDepthLayerAdjustment = i * .01f;
                        if (layerDepth == -1.0f)
                        {
                            Vector2 bottomOfTexture = position;
                            bottomOfTexture.Y = bottomOfTexture.Y - origin.Y + drawSize.Y;
                            layerDepth = Utilities.GetZByScreenPosition(position, PixelsInLevel.Y) - layerDepthLayerAdjustment;
                        }
                        // Adjust for tiles sitting next to each other at exactly the same z-values.
                        //else
                        //{
                        //    layerDepth -= layerDepthLayerAdjustment;
                        //    float xValue = position.X + 2.0f;
                        //    layerDepth += (1.0f / xValue) * 0.1f;
                        //}
                        // Assume that if the tile is meant to be walked on that it's a background tile.
                        if (currentTile.IsWalkable)
                            targetCollection = m_backgroundCollection;

                        Sprite.collisionActionDelegate collisionAction = TerrainCollisionAction;

                        // Create the Sprite for that tile, position it accordingly, and add it to the SpriteCollection
                        Sprite newTerrainSprite = new Sprite(texture, position, drawSize, origin, color, effects, layerDepth, collisionAction);
                        //m_terrainCollection.Add(newTerrainSprite);
                        targetCollection.Add(newTerrainSprite);
                        m_minimapColors.Add(newTerrainSprite, currentTile.MinimapColor);
                        if (currentTile.IsWalkable)
                            m_terrainGridPositions.Add(newTerrainSprite, new Vector2(r, c));
                    }
                }
            }
        }

        /// <summary>
        /// Checks all tiles between start and end looking for a vacant tile.
        /// 
        /// This function is used recursively in order to find an open tile by gradually increasing the size of the square to look over.
        /// 
        /// Using the same start and end will check that square before increasing the range.
        /// </summary>
        /// <param name="start">The upper-left corner of the rectangle of tiles to be checked for vacancy.</param>
        /// <param name="end">The lower-right corner of the rectangle of tiles to be checked for vacancy.</param>
        /// <param name="result">The Vector2 to assign the result to.</param>
        /// <returns>True if there is an open tile (anywhere...for now).</returns>
        private bool GetNearestOpenTile(Vector2 start, Vector2 end, out Vector2 result)
        {
            // If we're outside the range, return false and set result to an invalid vector.
            if (!PointExists((int)start.Y, (int)start.X) && !PointExists((int)end.Y, (int)end.X))
            {
                result = new Vector2(-1, -1);
                return false;
            }

            // Go through every tile in the range from start to end
            for (int r = (int)start.Y; r <= (int)end.Y; r++)
            {
                for (int c = (int)start.X; c <= (int)end.X; c++)
                {
                    // If the point exists and is available (not a wall), return it.
                    if (PointExists(r, c) && !IsWall(r, c))
                    {
                        result = GetOriginOfTile(r, c);
                        return true;
                    }
                }
            }

            // Otherwise, increase the size of the range and try again.
            Vector2 newStart = new Vector2(start.X - 1, start.Y - 1);
            Vector2 newEnd = new Vector2(end.X + 1, end.Y + 1);

            return GetNearestOpenTile(newStart, newEnd, out result);
        }

        /// <summary>
        /// Returns a Vector2 containing the width (X) and height (Y) of this Level object IN PIXELS.
        /// 
        /// This is useful for determining z-values across the Level.
        /// </summary>
        /// <returns>A Vector2 containing the width (X) and height (Y) of this Level object IN PIXELS.</returns>
        public Vector2 GetLevelSizeInPixels()
        {
            float widthInPixels = (m_tileOffset.X * m_drawScale * m_world.Width) + (m_tileOffset.X * m_drawScale * m_world.Height);
            float heightInPixels = (m_tileOffset.Y * m_drawScale * m_world.Width) + (m_tileOffset.Y * m_drawScale * m_world.Height);

            return new Vector2(widthInPixels, heightInPixels);
        }

        /// <summary>
        /// Determines if the provided grid position exists in the level.
        /// </summary>
        /// <param name="row">The row to check.</param>
        /// <param name="column">The column to check.</param>
        /// <returns>True if the grid position exists in the level.</returns>
        private bool PointExists(int row, int column)
        {
            if (row < m_world.Height && row >= 0 && column < m_world.Width && column >= 0)
                return true;

            return false;
        }

        #endregion

        #region Collision Actions

        /// <summary>
        /// When a terrain tile collides with another sprite, this function is called.
        /// </summary>
        /// <param name="sprite1">A sprite involved in the collision.</param>
        /// <param name="sprite2">The other sprite involved in the collision.</param>
        private void TerrainCollisionAction(Sprite sprite1, Sprite sprite2)
        {
            // TODO: Will anything be done when something collides with terrain?
        }

        /// <summary>
        /// When an interactable entity collides with another sprite, this function is called.
        /// </summary>
        /// <param name="sprite1">A sprite involved in the collision.</param>
        /// <param name="sprite2">The other sprite involved in the collision.</param>
        private void EntityCollisionAction(Sprite sprite1, Sprite sprite2)
        {
            var be1 = sprite1 as Unit;
            var be2 = sprite2 as Unit;

            // If the first sprite is a unit
            if (be1 != null)
            {
                // If the second sprite is also a unit
                if (be2 != null)
                {
                    //-------BE1 == UNIT && BE2 == UNIT--------
                    Unit lowUnit, highUnit;

                    if (be1.Priority > be2.Priority)
                    {
                        lowUnit = be2;
                        highUnit = be1;
                    }
                    else
                    {
                        lowUnit = be1;
                        highUnit = be2;
                    }

                    // Do some magic to make this faster
                    int magicNumber = (int)highUnit.TraversalState << 2;
                    magicNumber += (int)lowUnit.TraversalState;

                    switch (magicNumber)
                    {
                        case 0:
                            // Low = stopped
                            // High = stopped
                            Rectangle lowUnitCollisionRect = lowUnit.GetCollisionRectangle();
                            Rectangle highUnitCollisionRect = highUnit.GetCollisionRectangle();

                            Point lowUnitCollisionCenter = lowUnitCollisionRect.Center;
                            Point highUnitCollisionCenter = highUnitCollisionRect.Center;

                            Vector2 directionToHighUnit = new Vector2(highUnitCollisionCenter.X - lowUnitCollisionCenter.X,
                                                                      highUnitCollisionCenter.Y - lowUnitCollisionCenter.Y);

                            if (directionToHighUnit.Length() == 0.0f)
                            {
                                // The units are directly on top of each other, choose
                                // a random direction for the lower priority unit to go in
                                Random rgen = new Random();
                                directionToHighUnit.X = rgen.Next(1, 10);
                                directionToHighUnit.Y = rgen.Next(1, 10);

                                if (directionToHighUnit.X > 5)
                                {
                                    directionToHighUnit.X = -directionToHighUnit.X;
                                }
                                if (directionToHighUnit.Y > 5)
                                {
                                    directionToHighUnit.Y = -directionToHighUnit.Y;
                                }
                            }

                            Vector2 offset = new Vector2();

                            if (directionToHighUnit.Y < 0.0)
                            {
                                // Low is below high unit
                                offset.Y = lowUnitCollisionRect.Top - highUnitCollisionRect.Bottom;
                                offset.Y -= 1.0f;
                            }
                            else
                            {
                                // Low is above high unit
                                offset.Y = lowUnitCollisionRect.Bottom - highUnitCollisionRect.Top;
                                offset.Y += 1.0f;
                            }

                            if (directionToHighUnit.X > 0.0)
                            {
                                // Low is left of high unit
                                offset.X = highUnitCollisionRect.Left - lowUnitCollisionRect.Right;
                                offset.X -= 1.0f;
                            }
                            else
                            {
                                // Low is right of high unit
                                offset.X = highUnitCollisionRect.Right - lowUnitCollisionRect.Left;
                                offset.X += 1.0f;
                            }

                            Vector2 newPosition = lowUnit.Position + offset;
                            
                            Vector2 destTile;
                            Vector2 destPosition = new Vector2(newPosition.X + lowUnit.Size.X / 2.0f,
                                                               newPosition.Y + lowUnit.Size.Y);
                            GetGridPositionForPoint(destPosition, out destTile);

                            // The new position will make the unit go into unwalkable terrain
                            if (IsWall((int)destTile.X, (int)destTile.Y))
                            {
                                // Get a neighboring tile and go there
                                Vector2 currTile;
                                GetGridPositionForPoint(lowUnit.Position, out currTile);

                                Point currTilePoint = new Point((int)currTile.X, (int)currTile.Y);

                                List<Point> neighbors = new List<Point>();

                                for (int i = 1; i < 5; i++)
                                {
                                    neighbors.Add(new Point(currTilePoint.X + i, currTilePoint.Y + i));
                                    neighbors.Add(new Point(currTilePoint.X + i, currTilePoint.Y));
                                    neighbors.Add(new Point(currTilePoint.X + i, currTilePoint.Y - i));
                                    neighbors.Add(new Point(currTilePoint.X, currTilePoint.Y + i));
                                    neighbors.Add(new Point(currTilePoint.X, currTilePoint.Y - i));
                                    neighbors.Add(new Point(currTilePoint.X - i, currTilePoint.Y + i));
                                    neighbors.Add(new Point(currTilePoint.X - i, currTilePoint.Y));
                                    neighbors.Add(new Point(currTilePoint.X - i, currTilePoint.Y - i));
                                }

                                foreach (Point tilePos in neighbors)
                                {
                                    if (PointExists(tilePos.X, tilePos.Y) && !IsWall(tilePos.X, tilePos.Y))
                                    {
                                        // This is a valid tile, move there
                                        GetPointForGridPosition(tilePos, out newPosition);
                                        break;
                                    }
                                }
                            }

                            lowUnit.DestinationQueue.Enqueue(newPosition);
                            lowUnit.IsHandlingCollisions = true;

                            break;
                        case 1:
                            // Low = moving
                            // High = stopped
                            break;
                        case 2:
                            // Low = paused
                            // High = stopped
                            break;
                        case 4:
                            // Low = stopped
                            // High = moving
                            break;
                        case 5:
                            // Low = moving
                            // High = moving
                            break;
                        case 6:
                            // Low = paused
                            // High = moving
                            break;
                        case 8:
                            // Low = stopped
                            // High = paused
                            break;
                        case 9:
                            // Low = moving
                            // High = paused
                            break;
                        case 10:
                            // Low = paused
                            // High = paused
                            break;
                        default:
                            break;
                    }
                }
                // If the second sprite is NOT a unit
                else
                {
                    //-------BE1 == UNIT && BE2 != UNIT--------
                }
            }
            // If the first sprite is NOT a unit
            else
            {
                Console.WriteLine("NOT A UNIT");
            }

            /*
            if (unitToMove != null)
            {
                Vector2 pushVector = new Vector2(unitToMove.Position.X - sprite2.Position.X, unitToMove.Position.Y - sprite2.Position.Y);
                // Account for sprites directly on top of each other?

                float smoothFactor = unitToMove.Size.Length();

                smoothFactor /= 2.0f;

                Vector2 newPosition = new Vector2(sprite1.Position.X + pushVector.X / smoothFactor, sprite1.Position.Y + pushVector.Y / smoothFactor);

                unitToMove.Position = newPosition;
            }

            unitToMove = sprite2 as Unit;

            if (unitToMove != null)
            {
                Vector2 pushVector = new Vector2(unitToMove.Position.X - sprite1.Position.X, unitToMove.Position.Y - sprite1.Position.Y);
                // Account for sprites directly on top of each other?

                float smoothFactor = unitToMove.Size.Length();

                smoothFactor /= 2.0f;

                Vector2 newPosition = new Vector2(sprite2.Position.X + pushVector.X / smoothFactor, sprite2.Position.Y + pushVector.Y / smoothFactor);

                unitToMove.Position = newPosition;
            }
            */
        }

        /// <summary>
        /// When a non-interactable entity collides with another sprite, this function is called.
        /// </summary>
        /// <param name="sprite1">A sprite involved in the collision.</param>
        /// <param name="sprite2">The other sprite involved in the collision.</param>
        private void SpriteCollisionAction(Sprite sprite1, Sprite sprite2)
        {
            // TODO: Will anything be done when something collides with a Sprite (un-interactable object)?
        }

        #endregion

        #region Level/Player Interface Functions

        /// <summary>
        /// Adds the provided Player to this Level.  The Player is given a reference to this map so that it can build/draw on it.
        /// </summary>
        /// <param name="player">Player object to "add" to this level.</param>
        /// <returns>A boolean indicating whether or not there was an open space that this player could be added to.  If false, this player was not and cannot be added.</returns>
        public bool AddPlayer(Player player)
        {
            Vector2 startPosition;

            // Go get the next player start position if it exists; else return false.
            if (!m_world.ConsumeNextPlayerStartPoint(out startPosition))
                return false;

            // Set up the necessary starting variables for the new player.
            player.StartPosition = startPosition;
            player.TeamNumber = m_players.Count;
            // Set the player color
            if (player.TeamNumber == 0)
                player.TeamColor = Color.Yellow;
            else if (player.TeamNumber == 1)
                player.TeamColor = Color.Orange;
            else if (player.TeamNumber == 2)
                player.TeamColor = Color.Blue;
            else if (player.TeamNumber == 3)
                player.TeamColor = Color.Red;
            else
                player.TeamColor = new Color(new Vector3(player.TeamNumber * 50));

            // Add them to our list of players and set their Level accordingly.
            m_players.Add(player);
            player.SetLevel(this);

            // Now spawn the player with workers.
            player.SpawnWithWorkers();
            player.ScreenPosition = GetOriginOfTile((int)startPosition.Y, (int)startPosition.X);
            player.ScreenPosition.X -= ResolutionHandler.WindowWidth / 2;
            player.ScreenPosition.Y -= ResolutionHandler.WindowHeight / 2;

            return true;
        }

        #endregion

        #region Level/World/PathFinder Interface Functions

        /// <summary>
        /// Permanently adds a wall to the (row, column) position of the path-finding grid.
        /// 
        /// Use AddToWallMap(int row, int column, int lifeTime) to temporarily add a wall.
        /// </summary>
        /// <param name="row">The row of the wall to be added.</param>
        /// <param name="column">The column of the wall to be added.</param>
        private void AddToWallMap(int row, int column)
        {
            m_world.AddToWallMap(row, column);
        }

        /// <summary>
        /// Uses the provided row and column as a reference and then adds all of the positions in relativeWalls to the wall map.
        /// 
        /// For example, row = 5 and column = 5 where relative walls contains (0,0) will place a single wall at (5, 5).
        /// </summary>
        /// <param name="row">The row to be used as (0, 0).</param>
        /// <param name="column">The column to be used as (0, 0).</param>
        /// <param name="relativeWalls">The list of walls to place relative to the row and column.</param>
        private void AddToWallMap(int row, int column, List<Vector2> relativeWalls)
        {
            foreach (Vector2 v in relativeWalls)
            {
                AddToWallMap(row + (int)v.Y, column + (int)v.X);
            }
        }

        /// <summary>
        /// Permanently removes a wall from the (row, column) position of the path-finding grid.
        /// 
        /// Use RemoveFromWallMap(int row, int column, int lifeTime) to temporarily remove a wall.
        /// </summary>
        /// <param name="row">The row of the wall to be added.</param>
        /// <param name="column">The column of the wall to be added.</param>
        private void RemoveFromWallMap(int row, int column)
        {
            m_world.RemoveFromWallMap(row, column);
        }

        /// <summary>
        /// Uses the provided row and column as a reference and then removes all of the positions in relativeWalls to the wall map.
        /// 
        /// For example, row = 5 and column = 5 where relative walls contains (0,0) will remove a single wall at (5, 5).
        /// </summary>
        /// <param name="row">The row to be used as (0, 0).</param>
        /// <param name="column">The column to be used as (0, 0).</param>
        /// <param name="relativeWalls">The list of walls to remove relative to the row and column.</param>
        private void RemoveFromWallMap(int row, int column, List<Vector2> relativeWalls)
        {
            foreach (Vector2 v in relativeWalls)
            {
                RemoveFromWallMap(row + (int)v.Y, column + (int)v.X);
            }
        }

        /// <summary>
        /// Determines if the provided position is a wall and thus unwalkable.
        /// </summary>
        /// <param name="row">The row to check.</param>
        /// <param name="column">The column to check.</param>
        /// <returns>True if the position is a wall.</returns>
        private bool IsWall(int row, int column)
        {
            return m_world.IsWall(row, column);
        }

        /// <summary>
        /// Gets the list of tiles (as Points on the grid) for a path from one tile to another.
        /// </summary>
        /// <param name="startTile">The tile, in grid coordinates, where the path starts</param>
        /// <param name="endTile">The tile, in grid coordinates where the path ends (the destination)</param>
        /// <returns>Returns a list of Points that are grid positions for the tiles needed to walk to reach the
        /// destination tile.  Returns null if either the start or end tile is invalid or if the path does not exist.  The
        /// Point list will NOT include the start tile but does include the end tile.</returns>
        public List<Point> GetPath(Point startTile, Point endTile)
        {
            return m_world.GetPath(startTile, endTile);
        }

        #endregion

        #endregion
    }
}
